#include "FileUtils.h"

#include "log/Logging.h"

#include <cassert>
#include <cerrno>
#include <fcntl.h>
#include <cstdio>
#include <sys/stat.h>
#include <unistd.h>

FileUtils::AppendFile::AppendFile(StringArg filename)
        : fp_(::fopen(filename.data(), "ae")),  // 'e' for O_CLOEXEC
          writtenBytes_(0) {
    assert(fp_);
    ::setbuffer(fp_, buffer_, sizeof buffer_);
    // 将缓冲区设置为buffer_
    // posix_fadvise POSIX_FADV_DONTNEED ?
}

FileUtils::AppendFile::~AppendFile() {
    ::fclose(fp_);
    // 关闭文件描述符
}

void FileUtils::AppendFile::append(const char *logline, const size_t len) {
    size_t n = write(logline, len);
    size_t remain = len - n;
    // 计算出还未写入的长度
    while (remain > 0) {
        // 当数据未写入完成就一直循环写入
        size_t x = write(logline + n, remain);
        if (x == 0) {
            int err = ferror(fp_);
            if (err) {
                LOG_ERROR << "AppendFile::append() failed : " << std::strerror(err);
            }
            break;
        }
        n += x;
        remain = len - n; // remain -= x
    }

    writtenBytes_ += len;
}

void FileUtils::AppendFile::flush() {
    ::fflush(fp_);
}

size_t FileUtils::AppendFile::write(const char *logline, size_t len) {
    // #undef fwrite_unlocked
    return ::fwrite_unlocked(logline, 1, len, fp_);
    // 无锁版本的文件写入, 效率比普通的会快很多
}

FileUtils::ReadSmallFile::ReadSmallFile(StringArg filename)
        : fd_(::open(filename.data(), O_RDONLY | O_CLOEXEC)),
        // O_RDONLY: 以只读的方式打开,O_CLOEXEC: 标志位, 会在创建子进程的时候将子进程的文件描述符关闭
          err_(0) {
    buf_[0] = '\0';
    if (fd_ < 0) {
        // 打开失败
        err_ = errno;
    }
}

FileUtils::ReadSmallFile::~ReadSmallFile() {
    if (fd_ >= 0) {
        ::close(fd_); // FIXME: check EINTR
    }
}

// return errno
template<typename String>
int FileUtils::ReadSmallFile::readToString(int maxSize,
                                           String *content,
                                           int64_t *fileSize,
                                           int64_t *modifyTime,
                                           int64_t *createTime) {
    static_assert(sizeof(off_t) == 8, "_FILE_OFFSET_BITS = 64");
    assert(content != NULL);
    int err = err_;
    if (fd_ >= 0) {
        content->clear();

        if (fileSize) {
            struct stat statbuf;
            if (::fstat(fd_, &statbuf) == 0) {
                if (S_ISREG(statbuf.st_mode)) {
                    *fileSize = statbuf.st_size;
                    content->reserve(static_cast<int>(std::min(implicit_cast<int64_t>(maxSize), *fileSize)));
                } else if (S_ISDIR(statbuf.st_mode)) {
                    err = EISDIR;
                }
                if (modifyTime) {
                    *modifyTime = statbuf.st_mtime;
                }
                if (createTime) {
                    *createTime = statbuf.st_ctime;
                }
            } else {
                err = errno;
            }
        }

        while (content->size() < implicit_cast<size_t>(maxSize)) {
            size_t toRead = std::min(implicit_cast<size_t>(maxSize) - content->size(), sizeof(buf_));
            ssize_t n = ::read(fd_, buf_, toRead);
            if (n > 0) {
                content->append(buf_, n);
            } else {
                if (n < 0) {
                    err = errno;
                }
                break;
            }
        }
    }
    return err;
}

int FileUtils::ReadSmallFile::readToBuffer(int *size) {
    int err = err_;
    if (fd_ >= 0) {
        ssize_t n = ::pread(fd_, buf_, sizeof(buf_) - 1, 0);
        if (n >= 0) {
            if (size) {
                *size = static_cast<int>(n);
            }
            buf_[n] = '\0';
        } else {
            err = errno;
        }
    }
    return err;
}

template int FileUtils::readFile(StringArg filename,
                                 int maxSize,
                                 string *content,
                                 int64_t *, int64_t *, int64_t *);

template int FileUtils::ReadSmallFile::readToString(
        int maxSize,
        string *content,
        int64_t *, int64_t *, int64_t *);

